package com.xu.cloud.common.process.support;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xu.cloud.common.util.HttpServletUtil;
import com.xu.cloud.common.util.ReflectUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * POST & JavaBean请求参数解析器,
 * 支持处理json, x-www-form-urlencoded, form-data类型content-type,
 * 支持处理BaseRequest,BaseResponse类型实体类
 *
 * @author xuguan
 * @since 2025/2/20
 */
@SuppressWarnings({"FieldCanBeLocal", "NullableProblems"})
public class JavaBeanRequestResponseMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
    private final List<HttpMessageConverter<?>> converters;
    private final List<RequestBodyAdvice> requestBodyAdvice;
    private final List<ResponseBodyAdvice<?>> responseBodyAdvice;
    private final ObjectMapper objectMapper;
    private final List<Object> requestResponseBodyAdvice;
    private final MethodProcessor methodProcessor;

    public JavaBeanRequestResponseMethodProcessor(List<HttpMessageConverter<?>> converters,
                                                  List<RequestBodyAdvice> requestBodyAdvice,
                                                  List<ResponseBodyAdvice<?>> responseBodyAdvice,
                                                  ObjectMapper objectMapper) {
        this.converters = converters;
        this.requestBodyAdvice = requestBodyAdvice;
        this.responseBodyAdvice = responseBodyAdvice;
        this.objectMapper = objectMapper;

        this.requestResponseBodyAdvice = new ArrayList<>();
        this.requestResponseBodyAdvice.addAll(this.requestBodyAdvice);
        this.requestResponseBodyAdvice.addAll(this.responseBodyAdvice);
        this.methodProcessor = new MethodProcessor(this.converters, requestResponseBodyAdvice);
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return methodProcessor.supportsParameter(parameter);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return methodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return methodProcessor.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        methodProcessor.handleReturnValue(returnType, returnType, mavContainer, webRequest);
    }

    private class MethodProcessor extends RequestResponseBodyMethodProcessor {
        public MethodProcessor(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice) {
            super(converters, requestResponseBodyAdvice);
        }

        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return isPostOrPutMethod(HttpServletUtil.currentRequest()) && isJavaBean(parameter.getParameterType());
        }

        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            return isPostOrPutMethod(HttpServletUtil.currentRequest()) && isJavaBean(returnType.getParameterType());
        }

        @Override
        protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
                throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            Assert.state(servletRequest != null, "No HttpServletRequest");
            final PostServerHttpRequest inputMessage = new PostServerHttpRequest(servletRequest);
            inputMessage.setObjectMapper(objectMapper);

            Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
            if (arg == null && checkRequired(parameter)) {
                throw new HttpMessageNotReadableException("Required request body is missing: " +
                        parameter.getExecutable().toGenericString(), inputMessage);
            }
            return arg;
        }

        @Override
        protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType)
                throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
            Object body;
            try {
                EmptyBodyCheckingHttpInputMessage message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
                Class<?> contextClass = parameter.getContainingClass();
                Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
                if (targetClass == null) {
                    ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
                    targetClass = (Class<T>) resolvableType.resolve();
                }

                GenericHttpMessageConverter<?> genericConverter = null;
                for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                    if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                        genericConverter = (MappingJackson2HttpMessageConverter) messageConverter;
                    }
                }
                Assert.notNull(genericConverter, "MappingJackson2HttpMessageConverter not found");
                final Class<? extends GenericHttpMessageConverter> converterType = genericConverter.getClass();
                final Object adviceChain = ReflectUtil.instantiationClassForName(
                        "org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain",
                        new Class[]{List.class},
                        new Object[]{requestResponseBodyAdvice});
                if (message.hasBody()) {
                    HttpInputMessage msgToUse = ReflectUtil.invokeMethod(adviceChain,
                            "beforeBodyRead",
                            new Class[]{HttpInputMessage.class, MethodParameter.class, Type.class, Class.class},
                            new Object[]{message, parameter, targetType, converterType});
                    body = (genericConverter.read(targetType, contextClass, msgToUse));
                    body = ReflectUtil.invokeMethod(adviceChain,
                            "afterBodyRead",
                            new Class[]{Object.class, HttpInputMessage.class, MethodParameter.class, Type.class, Class.class},
                            new Object[]{body, msgToUse, parameter, targetType, converterType});
                } else {
                    body = ReflectUtil.invokeMethod(adviceChain,
                            "handleEmptyBody",
                            new Class[]{Object.class, HttpInputMessage.class, MethodParameter.class, Type.class, Class.class},
                            new Object[]{null, message, parameter, targetType, converterType});
                }
            } catch (IOException ex) {
                throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
            }
            return body;
        }

        @Override
        protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
            super.writeWithMessageConverters(value, returnType, webRequest);
        }

        @Override
        protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
            super.writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
        }

        protected boolean isPostOrPutMethod(@Nullable HttpServletRequest request) {
            if (Objects.isNull(request)) {
                return false;
            }
            return HttpMethod.POST.matches(request.getMethod()) || HttpMethod.PUT.matches(request.getMethod());
        }

        protected boolean isJavaBean(@Nullable Class<?> cls) {
            if (Objects.isNull(cls)) {
                return false;
            }

            if (BeanUtils.isSimpleProperty(cls)) {
                return false;
            }

            if (cls.isInterface()
                    || Modifier.isAbstract(cls.getModifiers())
                    || cls.isEnum()
                    || cls.isArray()
                    || cls.isAnnotation()
                    || cls.isSynthetic()
                    || cls.isPrimitive()) {
                return false;
            }

            // 有一个public字段
            for (Field field : cls.getFields()) {
                if (Modifier.isPublic(field.getModifiers())
                        && !Modifier.isStatic(field.getModifiers())) {
                    return true;
                }
            }

            // 有一个setter方法
            for (Method method : cls.getMethods()) {
                if (Modifier.isPublic(method.getModifiers())
                        && !Modifier.isStatic(method.getModifiers())
                        && method.getParameterCount() == 1
                        && method.getName().startsWith("set")) {
                    return true;
                }
            }

            return false;
        }
    }

}
